/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"){} you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.googlecode.httl.support.util;

import java.io.Reader;
import java.lang.reflect.Array;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * StringUtils. (Tool, Static, ThreadSafe)
 * 
 * @author Liang Fei (liangfei0201 AT gmail DOT com)
 */
public class StringUtils {

    private static final Pattern NUMBER_PATTERN = Pattern.compile("^[0-9]+(\\.[.0-9]+)?[BSILFDbsilfd]?$");
    
    public static boolean isNumber(String value) {
        return NUMBER_PATTERN.matcher(value).matches();
    }
    
    private static final Pattern NAMED_PATTERN = Pattern.compile("^[_A-Za-z][_0-9A-Za-z]*$");
    
    public static boolean isNamed(String value) {
        return NAMED_PATTERN.matcher(value).matches();
    }

    private static final Pattern TYPED_PATTERN = Pattern.compile("^[_A-Za-z][_.0-9A-Za-z]*$");
    
    public static boolean isTyped(String value) {
        return TYPED_PATTERN.matcher(value).matches();
    }

    private static final Pattern FUNCTION_PATTERN = Pattern.compile("^\\.[_A-Za-z][_0-9A-Za-z]*$");
    
    public static boolean isFunction(String value) {
        return FUNCTION_PATTERN.matcher(value).matches();
    }
    
    public static String toString(Object value) {
        if (value == null)
            return null;
        if (value.getClass().isArray()) {
            if (value instanceof boolean[]) {
                return Arrays.toString((boolean[]) value);
            } else if (value instanceof char[]) {
                return Arrays.toString((char[]) value);
            } else if (value instanceof byte[]) {
                return Arrays.toString((byte[]) value);
            } else if (value instanceof short[]) {
                return Arrays.toString((short[]) value);
            } else if (value instanceof int[]) {
                return Arrays.toString((int[]) value);
            } else if (value instanceof long[]) {
                return Arrays.toString((long[]) value);
            } else if (value instanceof float[]) {
                return Arrays.toString((float[]) value);
            } else if (value instanceof double[]) {
                return Arrays.toString((double[]) value);
            } else if (value instanceof Object[]) {
                return Arrays.toString((Object[]) value);
            } else {
                int len = Array.getLength(value);
                StringBuilder buf = new StringBuilder("[");
                for (int i = 0; i < len; i ++) {
                    if (i != 0) {
                        buf.append(", ");
                    }
                    buf.append(Array.get(value, i));
                }
                buf.append("]");
                return buf.toString();
            }
        }
        return String.valueOf(value);
    }
    
    public static String toByteString(byte[] bytes) {
        StringBuilder buf = new StringBuilder();
        for (byte b : bytes) {
            if (buf.length() > 0) {
                buf.append(", ");
            }
            buf.append((int)b);
        }
        return buf.toString();
    }

    public static String escapeString(String value) {
        if (value != null) {
            StringBuilder buf = null;
            for (int i = 0; i < value.length(); i ++) {
                char ch = value.charAt(i);
                String str;
                switch (ch) {
                    case '\\':
                        str = "\\\\";
                        break;
                    case '\"':
                        str = "\\\"";
                        break;
                    case '\'':
                        str = "\\\'";
                        break;
                    case '\t':
                        str = "\\t";
                        break;
                    case '\n':
                        str = "\\n";
                        break;
                    case '\r':
                        str = "\\r";
                        break;
                    case '\b':
                        str = "\\b";
                        break;
                    case '\f':
                        str = "\\f";
                        break;
                    default:
                        str = null;
                        break;
                }
                if (str != null) {
                    if (buf == null) {
                        buf = new StringBuilder();
                        if(i > 0) {
                            buf.append(value.substring(0, i));
                        }
                    }
                    buf.append(str);
                } else {
                    if (buf != null) {
                        buf.append(ch);
                    }
                }
            }
            if (buf != null) {
                return buf.toString();
            }
        }
        return value;
    }
    
    public static String unescapeString(String text) { // TODO 性能
        return text.replace("\\\"", "\"")
                    .replace("\\t", "\t")
                    .replace("\\n", "\n")
                    .replace("\\r", "\r")
                    .replace("\\b", "\b")
                    .replace("\\f", "\f")
                    .replace("\\\\", "\\");
    }

    public static String escapeHtml(String value) {
        if (value != null) {
            StringBuilder buf = null;
            for (int i = 0; i < value.length(); i ++) {
                char ch = value.charAt(i);
                String str;
                switch (ch) {
                    case '&':
                        str = "&amp;";
                        break;
                    case '<':
                        str = "&lt;";
                        break;
                    case '>':
                        str = "&gt;";
                        break;
                    case '\"':
                        str = "&quot;";
                        break;
                    case '\'':
                        str = "&apos;";
                        break;
                    default:
                        str = null;
                        break;
                }
                if (str != null) {
                    if (buf == null) {
                        buf = new StringBuilder();
                        if(i > 0) {
                            buf.append(value.substring(0, i));
                        }
                    }
                    buf.append(str);
                } else {
                    if (buf != null) {
                        buf.append(ch);
                    }
                }
            }
            if (buf != null) {
                return buf.toString();
            }
        }
        return value;
    }
    
    public static String unescapeHtml(String text) { // TODO 性能
        return text.replace("&lt;", "<")
                    .replace("&gt;", ">")
                    .replace("&quot;", "\"")
                    .replace("&apos;", "\'")
                    .replace("&amp;", "&");
    }
    
    public static String getConditionCode(Class<?> type, String code) throws ParseException {
        if (type != boolean.class) {
            if (type == byte.class 
                    || type == short.class
                    || type == int.class
                    || type == long.class
                    || type == float.class
                    || type == double.class) {
                code = "(" + code + ") != 0";
            } else if (type == char.class) {
                code = "(" + code + ") != \'\\0\'";
            } else if (type == String.class) {
                code = "(" + code + ").length() > 0";
            } else if (type.isArray()) {
                code = "(" + code + ").length > 0";
            } else if (Collection.class.isAssignableFrom(type)) {
                code = "(" + code + ").size() > 0";
            } else if (Map.class.isAssignableFrom(type)) {
                code = "(" + code + ").size() > 0";
            } else {
                String method = ClassUtils.getSizeMethod(type);
                if (method != null && method.length() > 0) {
                    code = "(" + code + ")." + method + " > 0";
                } else {
                    code = "(" + code + ") != null";
                }
            }
        }
        return code;
    }
    
    public static String getLocationMessage(Reader reader, int offset) {
        String location = "";
        if (offset <= 0) {
            return location;
        }
        try {
            int line = 1;
            int column = 0;
            int count = 0;
            int len = 0;
            char[] buf = new char[128];
            StringBuilder cur = new StringBuilder();
            while ((len = reader.read(buf)) > 0) {
                for (int i = 0; i < len; i ++) {
                    char ch = buf[i];
                    if (ch == '\n') {
                        line ++;
                        column = 0;
                        cur.setLength(0);
                    } else {
                        column ++;
                        cur.append(ch);
                    }
                    if (count >= offset) {
                        int padding = 20;
                        String before;
                        if (cur.length() <= padding) {
                            before = cur.toString();
                        } else {
                            before = cur.substring(cur.length() - padding);
                        }
                        int c = i + 1;
                        int remain = len - c;
                        StringBuilder after = new StringBuilder();
                        boolean breaked = false;
                        if (remain > 0) {
                            for (int j = c; j < padding + c && j < buf.length; j ++) {
                                if (buf[j]== '\r' || buf[j] == '\n') {
                                    breaked = true;
                                    break;
                                }
                                after.append(buf[j]);
                            }
                        }
                        if (! breaked && remain < padding) {
                            char[] b = new char[padding - remain];
                            int l = reader.read(b);
                            if (l > 0) {
                                for (int j = 0; j < l; j ++) {
                                    if (b[j] == '\r' || b[j] == '\n') {
                                        break;
                                    }
                                    after.append(b[j]);
                                }
                            }
                        }
                        StringBuilder msg = new StringBuilder();
                        msg.append("line: " + line + ", column: " + column + ", char: " + ch + ", in: \n");
                        for (int j = 0; j < padding * 2; j ++) {
                            msg.append("=");
                        }
                        msg.append("\n");
                        msg.append("...");
                        msg.append(before);
                        msg.append(after);
                        msg.append("...");
                        msg.append("\n");
                        for (int j = 0; j < before.length() + 2; j ++) {
                            msg.append(" ");
                        }
                        msg.append("^-here\n");
                        for (int j = 0; j < padding * 2; j ++) {
                            msg.append("=");
                        }
                        msg.append("\n");
                        return msg.toString();
                    }
                    count ++;
                }
            }
        } catch (Throwable t) {
        }
        return location;
    }
    
}
